package compiler;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import tokenizer.Token;
import tokenizer.Token.Type;
import tokenizer.Tokenizer;
import expr.Args;
import expr.AssignExpr;
import expr.DivideBinExpr;
import expr.Expr;
import expr.Fun;
import expr.Id;
import expr.MinusBinExpr;
import expr.Num;
import expr.PlusBinExpr;
import expr.SeqExpr;
import expr.TimesBinExpr;

public class Compiler {

	private Tokenizer tokenizer;

	public Compiler(BufferedReader br) throws IOException {
		tokenizer = new Tokenizer(br);
		next();
	}

	public Expr parse() throws IOException {
		prog();
		return null;
	}


	/**
	 * Sequenza di funzioni
	 * 
	 * @return
	 * @throws IOException
	 */
	private Fun prog() throws IOException {

		fun();
		if (tokenType() == Token.Type.Eos)
			System.out.println("\n*** Programma terminato ***");
		return null;
	}


	/**
	 * Ritorna una funzione
	 * 
	 * @return
	 * @throws IOException
	 */
	private Fun fun() throws IOException {
		List<Id> params = null;
		List<Id> locals = null;
		Expr expr = null;

		// {
		if (tokenType() != Token.Type.OpenBrace) {
			throw new RuntimeException("{ expected");
		}
		next();

		// ( Params ?
		if (tokenType() == Token.Type.OpenParen) {
			next();
			if (tokenType() != Token.Type.CloseParen){ 
				params=params();
			} // )
			next();
		}
		// Locals ?
		if (tokenType() != Token.Type.To) { //or close brace
			locals=locals();
		}
		if (tokenType() == Token.Type.To && next().type() == Token.Type.CloseBrace) {
			throw new RuntimeException("sequence expression expected");
		}
		expr = sequenceExpression();
		if (tokenType() != Token.Type.CloseBrace)
			throw new RuntimeException("} expected");
		next();
		return new Fun(params, locals, expr);
	}

	private List<Id> locals() throws IOException {
		List<Id> temp = new ArrayList<Id>();
		while(tokenType() != Type.To) {
			if (tokenType() == Type.Ident) {
				temp.add(new Id(tokenizer.token().name()));
				next();
			}
			if (tokenType() == Type.Comma) {
				next();
			}
			if (tokenType() == Type.CloseBrace) {			
				break;
			}
		}

		return temp;
	}


	/**
	 * Lista di parametri
	 * 
	 * @return
	 * @throws IOException
	 */
	private List<Id> params() throws IOException {
		List<Id> temp = new ArrayList<Id>();
		while(tokenType() != Type.CloseParen){
			if (tokenType() == Type.Ident){
				temp.add(new Id(tokenizer.token().name()));
				next();
			}//
			if(tokenType() == Type.Comma){
				next();

			}
		}
		return temp;
	}


	/**
	 * Aggiunge i parametri ad una lista di Params
	 * 
	 * @return
	 */
	private Id addParams() {
		return new Id(tokenizer.token().name());
	}


	/**
	 * Aggiunge le variabili locali ad una lista di Locals
	 * @return
	 */
	private Id addLocals() {
		return new Id(tokenizer.token().name());
	}

	private Expr sequenceExpression() throws IOException {
		List<Expr> seqExpr = new ArrayList<Expr>();

		seqExpr.add(assignExpr());
		while (tokenType() == Token.Type.Semicolon) {
			//			while (tokenType() == Token.Type.Semicolon)
			//				next();
			next();
			seqExpr.add(assignExpr());
		}
		System.out.print("espressioni trovate:");
		print(seqExpr);
		return seqExpr.size() == 1 ? seqExpr.get(0) : new SeqExpr(seqExpr.toArray(new Expr[seqExpr.size()]));

	}

	/**
	 * Avanza di un token
	 * @throws IOException
	 */
	private Token next() throws IOException {
		tokenizer.next();
		System.out.println(tokenizer.token().type().toString());
		return tokenizer.token();
	}

	private void print(List<Expr> expr) {
		for (int i = 0; i < expr.size(); i++) {
			System.out.println(expr.get(i).toString());
		}
	}

	private Expr assignExpr() throws IOException {
		if (tokenType() == Token.Type.Ident) {
			String idTemp = tokenizer.token().name();
			next();
			if (isAssignOp()) {
				String opTemp = tokenizer.token().type().toString();
				next();
				return new AssignExpr(new Id(idTemp), opTemp, assignExpr());
			}
		}
		//non chiamare addExpr() ma orExpr()		
		//		return addExpr();
		return orExpr();
	}

	private Expr orExpr() throws IOException {
		Expr left = andExpr();
		while (isOrOp()) {
			next();
			left = andExpr();
		}
		return left;
	}

	private boolean isOrOp() {
		switch (tokenType()) {
		case LogOr:
			return true;
		default:
			return false;
		}
	}

	private Expr andExpr() throws IOException {
		Expr left = eqExpr();
		while (isAndOp()) {
			next();
			left = eqExpr();
		}
		return left;
	}

	private boolean isAndOp() {
		switch (tokenType()) {
		case LogAnd:
			return true;
		default:
			return false;
		}
	}

	private Expr eqExpr() throws IOException {
		Expr left = relExpr();
		if (isEqOp()) {
			next();
			left = relExpr();
		}
		return left;
	}

	private boolean isEqOp() {
		switch (tokenType()) {
		case Eq:
		case Ne:
			return true;
		default:
			return false;
		}
	}

	private Expr relExpr() throws IOException {
		Expr left = addExpr();
		if (isRelOp()) {
			next();
			left = addExpr();
		}
		return left;
	}
	
	private boolean isRelOp() {
		switch (tokenType()) {
		case Lt:
		case Le:
		case Gt:
		case Ge:
			return true;
		default:
			return false;
		}
	}
	
	private Expr addExpr() throws IOException {
		Expr left = mulExpr();
		while (isAddOp()) {
			next();
			switch (tokenType()) {
			case Plus:
				left = new PlusBinExpr(left, mulExpr());
			case Minus:
				left = new MinusBinExpr(left, mulExpr());
			}
		}
		return left;
	}

	private boolean isAddOp() {
		return tokenType() == Type.Plus || tokenType() == Type.Minus;
	}
	
	private Expr mulExpr() throws IOException {
		Expr left = unaryExpr();
		next();
		while (isMulOp()) {
			switch (tokenType()) {
			case Times:
				left = new TimesBinExpr(left, unaryExpr());
			case Div:
				left = new DivideBinExpr(left, unaryExpr());
			}
			next();
		}
		return left;
	}

	private boolean isMulOp() {

		switch (tokenType()) {
		case Times:
		case Div:
		case Mod:
			return true;
		default:
			return false;
		}
	}

	private Expr unaryExpr() throws IOException {
		if (isUnaryOp())
			return unaryExpr();
		else 
			return postfixExpr();
	}

	private boolean isUnaryOp() {

		switch (tokenType()) {
		case Plus:
		case Minus:
		case Not:
			return true;
		default:
			return false;
		}
	}

	private Expr postfixExpr() throws IOException {
		Expr temp = primaryExpr();
		next();
		if (tokenType() == Token.Type.OpenParen)
			temp = args();
		prev();
		return temp;
	}
	
	private Expr primaryExpr() throws IOException {

		switch (tokenType()) {
		case Ident:
			return new Id(tokenizer.token().name());
		case Num:
			return new Num(tokenizer.token().num());
		case OpenBrace:
			return fun();
		default:
			throw new RuntimeException(
					"primaryExpr(): nessuna primaryExpr trovata");
		}
	}

	private Expr args() throws IOException {
		Args args= new Args(null);
		if (tokenType() == Token.Type.OpenParen) {
			Expr expr = sequenceExpression();
			args.add(expr);
			while (tokenType() == Token.Type.Comma) {
			args.add(sequenceExpression());
			}
		}
		if (tokenType() == Token.Type.CloseParen) {
			return args;
		}
		return args;
	}

	private boolean isAssignOp() {

		switch (tokenType()) {

		case Becomes:
		case PlusBecomes:
		case MinusBecomes:
		case TimesBecomes:
		case DivBecomes:
			return true;

		default:
			return false;
		}
	}


	private Type tokenType() {
		return tokenizer.token().type();
	}
	
	private void prev() {
		this.tokenizer.prev();
	}

}
